﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Eventable;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;

namespace Eventable
{
    public static class Contracts
    {
        public struct Changing
        {
            /// <summary>
            /// Установка зависимости проверки/получения-значения одного свойства (ведомого) от другого (ведущего)
            /// </summary>
            /// <typeparam name="TmProp">Тип ведущего свойства</typeparam>
            /// <typeparam name="TdProp">Тип ведомого свойства</typeparam>
            /// <param name="master">Владелец свойства</param>
            /// <param name="mainProp">Наименование ведущего свойства</param>
            /// <param name="depProp">Наименование ведомого свойства</param>
            /// <param name="getDep">Функция вычисления ведомого свойства на основе ведущего</param>
            public static void LinkVerifyDependProperty<TmProp, TdProp>(IEventProvider master, string mainProp, string depProp, Func<TmProp, TdProp> getDep)
            {
                if (master == null) throw new ArgumentNullException("master");
                if (mainProp == null) throw new ArgumentNullException("mainProp");
                if (depProp == null) throw new ArgumentNullException("depProp");
                if (getDep == null) throw new ArgumentNullException("getDep");
                Contract.EndContractBlock();
                master.ListenEvent(master,
                                   (sender, evName, args) =>
                                   master.NotifyEvent(depProp + "Changing",
													  RulesArgs.SelectOldNewValue(args, getDep))
                                   , mainProp + "Changing");
            }

            /// <summary>
            /// Установка зависимости проверки/получения-значения ведомого свойства от другого свойства-коллекции
            /// </summary>
            /// <typeparam name="TcItem">Тип элемента коллекции</typeparam>
            /// <typeparam name="TdProp">Тип ведомого свойства</typeparam>
            /// <param name="master">Владелец свойства</param>
            /// <param name="collProp">Наименование свойства-коллекции</param>
            /// <param name="depProp">Наименование ведомого свойства</param>
            /// <param name="getCurDep">Получение текущего значения зависимого свойства</param>
            /// <param name="addFunc">Функция агрегации нового значения (добавление)</param>
            /// <param name="removeFunc">Функция агрегации нового значения (удаление)</param>
            /// <exception cref="ArgumentNullException"></exception>
            public static void LinkDependPropFromCollection<TcItem, TdProp>(IEventProvider master, string collProp, string depProp, Func<TdProp> getCurDep, Func<TdProp, TcItem, TdProp> addFunc, Func<TdProp, TcItem, TdProp> removeFunc)
            {
                if (master == null) throw new ArgumentNullException("master");
                if (depProp.IsEmpty()) throw new ArgumentNullException("depProp");
                if (collProp.IsEmpty()) throw new ArgumentNullException("collProp");
                if (getCurDep == null) throw new ArgumentNullException("getCurDep");
                if (addFunc == null) throw new ArgumentNullException("addFunc");
                if (removeFunc == null) throw new ArgumentNullException("removeFunc");
                Contract.EndContractBlock();
                master.ListenEvent(master,
                                   (sender, evName, args) =>
                                       {
                                           TdProp ch, cur = ch = getCurDep();
                                           ch = RulesArgs.Collection.GetAdd<TcItem>(args).Aggregate(ch, addFunc);
                                           ch = RulesArgs.Collection.GetRemove<TcItem>(args).Aggregate(ch, removeFunc);
                                           return master.NotifyEvent(depProp + "Changing",
                                                                     RulesArgs.ForChangeValue(cur, ch));
                                       }
                                   , collProp + "Changing");
            }            
        }

        public struct Changed
        {
            ///// <summary>
            ///// Отключение прослушивания объекта по факту отключения его от свойства
            ///// </summary>
            ///// <param name="master">Владелец свойства</param>
            ///// <param name="propName">Наименование свойства</param>
            //public static void DettachObjectFromOldVaue(IEventProvider master, string propName)
            //{
            //    master.ListenEvent(master,
            //        (sender, evName, args) =>
            //        {
            //            (RulesArgs.OldRefGet<IEventProvider>(args) ?? NullEventProvider.Singleton).DettachObject(master);
            //            return true;
            //        },
            //        propName + "Changed");
            //}

            /// <summary>
            /// 
            /// </summary>
            /// <param name="master"></param>
            /// <param name="collProp"></param>
            /// <param name="innerProp"></param>
            /// <param name="evIng"></param>
            /// <param name="evEd"></param>
            public static void LinkAttachDettachCollectionItem(IEventProvider master, string collProp, string innerProp, EvntHandler evIng, EvntHandler evEd)
            {
                if (master == null) throw new ArgumentNullException("master");
                if (collProp == null) throw new ArgumentNullException("collProp");
                if (innerProp == null) throw new ArgumentNullException("innerProp");
                Contract.EndContractBlock();
                if (evIng != null)
                    master.ListenEvent(master,
                    (sender, evName, args) =>
                    {
                        foreach (var item in RulesArgs.Collection.GetRemove<IEventProvider>(args))
                            item.UnlistenEvent(master, evIng, innerProp + "Changing");

                        foreach (var item in RulesArgs.Collection.GetAdd<IEventProvider>(args))
                            item.ListenEvent(master, evIng, innerProp + "Changing");
                        return true;
                    }
                , collProp + "Changed");

                if (evEd != null)
                    master.ListenEvent(master,
                    (sender, evName, args) =>
                    {
                        foreach (var item in RulesArgs.Collection.GetRemove<IEventProvider>(args))
                            item.UnlistenEvent(master, evEd, innerProp + "Changed");

                        foreach (var item in RulesArgs.Collection.GetAdd<IEventProvider>(args))
                            item.ListenEvent(master, evEd, innerProp + "Changed");
                        return true; 
                    }
                , collProp + "Changed");
            }

            /// <summary>
            /// Подключение автоматической привязки прослушивания 
            /// </summary>
            /// <param name="master">Владелец свойства</param>
            /// <param name="mainProp">Наименование основного свойства</param>
            /// <param name="innerProp">Наименование внутреннего свойства</param>
            /// <param name="evIng">Делегат проверки</param>
            /// <param name="evEd">Делегат установки</param>
            public static void LinkAttachDettachItem(IEventProvider master, string mainProp, string innerProp, EvntHandler evIng, EvntHandler evEd)
            {                
                if (evIng != null)
                    master.ListenEvent(master,
                    (sender, evName, args) =>
                    {
                        var oldInner = RulesArgs.OldRefGet<IEventProvider>(args);
                        if (oldInner != null)
                            oldInner.UnlistenEvent(master, evIng, innerProp + "Changing");

                        var newInner = RulesArgs.NewRefGet<IEventProvider>(args);
                        if (newInner != null)
                            newInner.ListenEvent(master, evIng, innerProp + "Changing");

                        return true;
                    },
                    mainProp + "Changed");

                if (evEd != null)
                    master.ListenEvent(master,
                    (sender, evName, args) =>
                    {
                        var oldInner = RulesArgs.OldRefGet<IEventProvider>(args);
                        if (oldInner != null)
                            oldInner.UnlistenEvent(master, evEd, innerProp + "Changed");

                        var newInner = RulesArgs.NewRefGet<IEventProvider>(args);
                        if (newInner != null)
                            newInner.ListenEvent(master, evEd, innerProp + "Changed");

                        return true;
                    },
                    mainProp + "Changed");
            }

            /// <summary>
            /// Подключение автоматической привязки/отвязки для свойств подключаемых/отключаемых элементов коллекции
            /// </summary>
            /// <typeparam name="TcItem">Тип элемента коллекции</typeparam>
            /// <typeparam name="TdProp">Тип зависимого свойства</typeparam>
            /// <param name="master">Владелец свойства</param>
            /// <param name="collProp">Наименование свойства-коллекции</param>
            /// <param name="depProp">Наименование зависимого свойства</param>
            /// <param name="getCurDep">Получение текущего значения зависимого свойства</param>
            /// <param name="addFunc">Функция агрегации добавляемого значение</param>
            /// <param name="removeFunc">Функция агрегации удаляемого значения</param>
            public static void LinkNotifyFromDependCollection<TcItem, TdProp>(IEventProvider master, string collProp, string depProp, Func<TdProp> getCurDep, Func<TdProp, TcItem, TdProp> addFunc, Func<TdProp, TcItem, TdProp> removeFunc)
            {
                master.ListenEvent(master,
                    (sender, evName, args) =>
                    {
                        TdProp ch, cur = ch = getCurDep();
                        ch = RulesArgs.Collection.GetAdd<TcItem>(args).Aggregate(ch, removeFunc);
                        ch = RulesArgs.Collection.GetRemove<TcItem>(args).Aggregate(ch, addFunc);
                        return master.TryAllNotifyEvent(depProp + "Changed", RulesArgs.ForChangeValue(ch, cur));
                    }
                , collProp + "Changed");
            }

            //public static void LinkNotifyFromDependCollection<TcItem, TdProp>(IEventProvider master, string collNameChanged, string propNameDepend, Func<TcItem> curCollFunc, Func<TdProp, TcItem, TdProp> addFunc)
            //{
            //    master.ListenEvent(master,
            //        (sender, evName, args) =>
            //            master.TryAllNotifyEvent(propNameDepend + "Changed", RulesArgs.ForChangeValue(getDep(RulesArgs.Collection.GetAdd<TcItem>(args),
            //                                                                                     RulesArgs.Collection.GetRemove<TcItem>(args)), getCurDep())),
            //        collNameChanged + "Changed");
            //}

            /// <summary>
            /// Подключение автоматической привязки/отвязки для подсвойств
            /// </summary>
            /// <typeparam name="T">Тип основного свойства</typeparam>
            /// <typeparam name="TdProp">Тип подсвойства</typeparam>
            /// <param name="master">Владелец свойства</param>
            /// <param name="propNameChanged">Наименование основного свойства</param>
            /// <param name="propNameDepend">Наименование зависимого свойства</param>
            /// <param name="selectFunc">Функция преобразования</param>
            public static void LinkNotifyFromDependProperty<T, TdProp>(IEventProvider master, string propNameChanged, string propNameDepend, Func<T, TdProp> selectFunc)
            {
                master.ListenEvent(master,
                    (sender, evName, args) =>
                        master.TryAllNotifyEvent(propNameDepend + "Changed", RulesArgs.SelectOldNewValue(args, selectFunc)),
                    propNameChanged + "Changed");
            }

            //TODO public static void LinkNotifyInnerOperation<Tmain>(IEventProvider master, string mainProp, string innerOp, Action proc)
            //{
            //    if (proc != null)
            //        master.ListenEvent(master,
            //        (sender, evName, args) =>
            //        {
            //            var oldInner = RulesArgs.OldRefGet<IEventProvider>(args);
            //            if (oldInner != null)
            //                oldInner.UnlistenEvent(master, ()=>, innerOp);

            //            var newInner = RulesArgs.NewRefGet<IEventProvider>(args);
            //            if (newInner != null)
            //                newInner.ListenEvent(master, evEd, innerOp);

            //            return true;
            //        },
            //        mainProp + "Changed");
            //}
        }

        public struct Property
        {
            /// <summary>
            /// Установить ограничение для свойства
            /// </summary>
            /// <typeparam name="T">Тип свойства</typeparam>
            /// <param name="master">Владелец свойства</param>
            /// <param name="propName">Наименование свойства</param>
            /// <param name="restFunc">Ограничивающая функция</param>
            /// <param name="getEx">Исключение генерируемое при не соответствии</param>
            public static void Restrict<T>(IEventProvider master, string propName, Func<T, bool> restFunc, Func<Exception> getEx)
            {
                master.ListenEvent(master,
                    (sender, evName, args) =>
                    {
                        if (restFunc(RulesArgs.NewValuedGet<T>(args)))
                            return true;
                        else
                            throw (getEx() ?? new Exception("Свойство {0} ограничено".goFormat(propName)));
                    }
                , propName + "Changing");
            }

        	/// <summary>
        	/// Установить ограничение для свойства
        	/// </summary>
        	/// <typeparam name="T">Тип свойства</typeparam>
        	/// <typeparam name="M"></typeparam>
        	/// <param name="master">Владелец свойства</param>
        	/// <param name="propName">Наименование свойства</param>
        	/// <param name="restFunc">Ограничивающая функция</param>
        	/// <param name="getEx">Исключение генерируемое при не соответствии</param>
        	public static void Restrict<M, T>(M master, string propName, Func<T, bool> restFunc, Func<M, Exception> getEx)
				where M : IEventProvider
			{
				master.ListenEvent(master,
					(sender, evName, args) =>
					{
						if (restFunc(RulesArgs.NewValuedGet<T>(args)))
							return true;
						else
							throw (getEx(master) ?? new Exception("Свойство {0} ограничено".goFormat(propName)));
					}
				, propName + "Changing");
			}

            /// <summary>
            /// Привязка атомарной массовой привязки/отвязки подсвойств элементов коллекции
            /// </summary>
            /// <typeparam name="TcItem">Тип элемента коллекции</typeparam>
            /// <typeparam name="Ti">Тип подсвойства</typeparam>
            /// <param name="master">Владелец</param>
            /// <param name="collProp">Наименование свойства-коллекции</param>
            /// <param name="innerProp">Наименование подсвойства</param>
            /// <param name="innerFunc">Функция значения подсвойства непосредственно из элемента</param>
            /// <param name="restFunc">Ограничивающая функция</param>
            /// <param name="getEx">Функция получения исключения</param>
            /// <param name="changeInner">Процедура замены значения подсвойства</param>
            public static void RestrictAtomicInner<TcItem, Ti>(IEventProvider master, string collProp, string innerProp, Func<TcItem, Ti> innerFunc, Func<Ti, bool> restFunc, Func<Ti, Exception> getEx, Action<TcItem, Ti, Ti> changeInner = null)
            {
                master.ListenEvent(master,
                    (sender, evName, args) =>
                    {
                        var ch = RulesArgs.Collection.GetAdd<TcItem>(args)
                            .Select(innerFunc)
                            .Except(RulesArgs.Collection.GetRemove<TcItem>(args)
                                .Select(innerFunc));
                        foreach (var item in ch)
                            if (!restFunc(item))
                                throw (getEx(item) ?? new Exception("Свойство {0} ограничено: {1}".goFormat(innerProp, item)));
                        return true;
                    }
                , collProp + "Changing");

                Changed.LinkAttachDettachCollectionItem(master, collProp, innerProp,
                    (sender1, evName1, args1) =>
                    {
                        var newValue = RulesArgs.NewValuedGet<Ti>(args1);
                        if (!restFunc(newValue))
                            throw (getEx(newValue) ?? new Exception("Свойство {0} ограничено: {1}".goFormat(innerProp, newValue)));
                        else return true;
                    },
                    (changeInner == null) ? null : (EvntHandler)((sender1, evName1, args1) =>
                    {
                        changeInner((TcItem)sender1, RulesArgs.OldValuedGet<Ti>(args1), RulesArgs.NewValuedGet<Ti>(args1));
                        return true;
                    })
                    );
            }

            public static void RestrictInner<TcItem, Ti>(IEventProvider master, string mainProp, string innerProp,
                                                         Func<TcItem, Ti> innerFunc, Func<Ti, bool> restFunc,
                                                         Func<Ti, Exception> getEx,
                                                         Action<TcItem, Ti, Ti> changeInner = null)
            {
                master.ListenEvent(master,
                    (sender, evName, args) =>
                    {
                        var ch = innerFunc(RulesArgs.NewValuedGet<TcItem>(args));                        
                            if (!restFunc(ch))
                                throw (getEx(ch) ?? new Exception("Свойство {0} ограничено: {1}".goFormat(innerProp, ch)));
                        return true;
                    }
                , mainProp + "Changing");

                Changed.LinkAttachDettachItem(master, mainProp, innerProp,
                    (sender1, evName1, args1) =>
                    {
                        var newValue = RulesArgs.NewValuedGet<Ti>(args1);
                        if (!restFunc(newValue))
                            throw (getEx(newValue) ?? new Exception("Свойство {0} ограничено: {1}".goFormat(innerProp, newValue)));
                        else return true;
                    },
                    (changeInner == null) ? null : (EvntHandler)((sender1, evName1, args1) =>
                    {
                        changeInner((TcItem)sender1, RulesArgs.OldValuedGet<Ti>(args1), RulesArgs.NewValuedGet<Ti>(args1));
                        return true;
                    })
                    );
            }

            public static void RestrictAdd<TcItem>(IEventProvider master, string propName, Func<TcItem, bool> restFunc, Func<TcItem, Exception> getEx)
            {
                master.ListenEvent(master,
                    (sender, evName, args) =>
                    	{
                    		foreach (var item in RulesArgs.Collection.GetAdd<TcItem>(args).Where(s => !restFunc(s)))
                    		{
                    			throw (getEx(item) ?? new Exception("Свойство {0} ограничено".goFormat(propName)));
                    		}
                    		return true;
                    	}
                                   , propName + "Changing");
            }

            /// <summary>
            /// Установить прямую зависимость между свойствами (ведущий-ведомый)
            /// </summary>
            /// <typeparam name="TmProp">Тип ведущего свойства</typeparam>
            /// <typeparam name="TdProp">Тип ведомого свойства</typeparam>
            /// <param name="master">Владелец свойства</param>
            /// <param name="mainProp">Наименование ведущего свойства</param>
            /// <param name="depProp">Наименование ведомого свойства</param>
            /// <param name="getDep">Функция вычисления ведомого свойства на основе ведущего</param>
            public static void Depend<TmProp, TdProp>(IEventProvider master, string mainProp, string depProp, Func<TmProp, TdProp> getDep)
            {
                Changing.LinkVerifyDependProperty(master, mainProp, depProp, getDep);
                Changed.LinkNotifyFromDependProperty(master, mainProp, depProp, getDep);
            }

            public static void DependFromCollection<TcItem, TdProp>(IEventProvider master, string collProp,
                                                                    string depProp, Func<TdProp> curFunc,
                                                                    Func<TdProp, TcItem, TdProp> addFunc,
                                                                    Func<TdProp, TcItem, TdProp> removeFunc)
            {
                Changing.LinkDependPropFromCollection(master, collProp, depProp, curFunc, addFunc, removeFunc);
                Changed.LinkNotifyFromDependCollection(master, collProp, depProp, curFunc, addFunc, removeFunc);
            }

            public static void DependFromCollectionInner<TcItem, TdProp, TiProp>(IEventProvider master, string collProp,
                                                                                 string depProp, string innerProp,
                                                                                 Func<TdProp> curFunc,
                                                                                 Func<TcItem, TiProp> getInner,
                                                                                 Func<TdProp, TiProp, TdProp> addFunc,
                                                                                 Func<TdProp, TiProp, TdProp> removeFunc)
            {
                DependFromCollection<TcItem, TdProp>(master, collProp, depProp, curFunc, (d, i) => addFunc(d, getInner(i)), (d, i) => removeFunc(d, getInner(i)));
                Changed.LinkAttachDettachCollectionItem(master, collProp, innerProp,
                    (sender1, evName1, args1) => master.NotifyEvent(depProp + "Changing", RulesArgs.ForChangeValue(curFunc(), addFunc(removeFunc(curFunc(), RulesArgs.OldValuedGet<TiProp>(args1)), RulesArgs.NewValuedGet<TiProp>(args1)))),
                    (sender1, evName1, args1) => master.TryNotifyEvent(depProp + "Changed", RulesArgs.ForChangeValue(addFunc(removeFunc(curFunc(), RulesArgs.NewValuedGet<TiProp>(args1)), RulesArgs.OldValuedGet<TiProp>(args1)), curFunc())));
            }

            /// <summary>
            /// Установить прямую зависимость одним свойством и подсвойством другого (ведущий-ведомый)
            /// </summary>
            /// <typeparam name="TmProp">Тип ведущего свойства</typeparam>
            /// <typeparam name="TdProp">Тип ведомого свойства</typeparam>
            /// <typeparam name="TiProp">Тип подсвойства ведущего свойства</typeparam>
            /// <param name="master">Владелец свойства</param>
            /// <param name="mainProp">Наименование ведущего свойства</param>
            /// <param name="depProp">Наименование ведомого свойства</param>
            /// <param name="innerProp">Наименование подсвойства</param>
            /// <param name="getInner">Функция вычисления подсвойства на основе ведущего свойства</param>
            /// <param name="getDep">Функция вычисления ведомого свойства на основе подсвойства</param>
            public static void DependInner<TmProp, TdProp, TiProp>(IEventProvider master, string mainProp, string depProp, string innerProp, Func<TmProp, TiProp> getInner, Func<TiProp, TdProp> getDep)
            {
                //<IData, long, long>(this, "Data", "FreeSpace", "Size", s => s.Size, s => Volume - s);
                Depend<TmProp, TdProp>(master, mainProp, depProp, s => getDep(getInner(s)));
                Changed.LinkAttachDettachItem(master, mainProp, innerProp,
                    (sender1, evName1, args1) => master.NotifyEvent(depProp + "Changing", RulesArgs.SelectOldNewValue(args1, getDep)),
                    (sender1, evName1, args1) => master.TryAllNotifyEvent(depProp + "Changed", RulesArgs.SelectOldNewValue(args1, getDep)));
            }
        }
    }
}
